#region Using Directives

using System;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Image = System.Web.UI.WebControls.Image;
using System.Web;
using System.Collections;
using System.Data;

#endregion

namespace Web.Google
{
    [ParseChildren(true), PersistChildren(false)]
    public class Chart : DataBoundControl
    {
        #region Fields

        private static readonly string ServiceUrl = "http://chart.apis.google.com/chart";
        private string simpleEncoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        private DataSetCollection dataSets = null;
        private FillCollection fills = null;

        #endregion

        #region Properties

        public ChartType Type
        {
            get
            {
                object o = ViewState["Type"];
                return o == null ? ChartType.Line : (ChartType)o;
            }
            set { ViewState["Type"] = value; }
        }

        public ChartDataEncoding Encoding
        {
            get
            {
                object o = ViewState["Encoding"];
                return o == null ? ChartDataEncoding.Auto : (ChartDataEncoding)o;
            }
            set { ViewState["Encoding"] = value; }
        }

        public string Title
        {
            get
            {
                object o = ViewState["Title"];
                return o == null ? String.Empty : (string)o;
            }
            set { ViewState["Title"] = value; }
        }

        [
            MergableProperty(false),
            PersistenceMode(PersistenceMode.InnerProperty),
        ]
        public DataSetCollection DataSets
        {
            get
            {
                if (dataSets == null)
                {
                    dataSets = new DataSetCollection();
                    if (base.IsTrackingViewState)
                    {
                        dataSets.TrackViewState();
                    }
                }
                return dataSets;
            }
        }

        public bool EnableLegend
        {
            get
            {
                object o = ViewState["EnableLegend"];
                return o == null ? false : (bool)o;
            }
            set { ViewState["EnableLegend"] = value; }
        }

        public AxisType Axis
        {
            get
            {
                object o = ViewState["Axis"];
                return o == null ? AxisType.None : (AxisType)o;
            }
            set { ViewState["Axis"] = value; }
        }

        [
            MergableProperty(false),
            PersistenceMode(PersistenceMode.InnerProperty),
        ]
        public FillCollection Fills
        {
            get
            {
                if (fills == null)
                {
                    fills = new FillCollection();
                    if (base.IsTrackingViewState)
                    {
                        fills.TrackViewState();
                    }
                }
                return fills;
            }
        }

        #region Checks for Features according to Chart Type

        private bool MarkersAllowed
        {
            get
            {
                return Type == ChartType.Line ||
                    Type == ChartType.Scatter;
            }
        }

        private bool MultipleAxisAllowed
        {
            get
            {
                return Type == ChartType.Line ||
                    Type == ChartType.Scatter ||
                    Type == ChartType.HorizontalBar ||
                    Type == ChartType.HorizontalBarGrouped ||
                    Type == ChartType.VerticalBar ||
                    Type == ChartType.VerticalBarGrouped;
            }
        }

        private bool GridLinesAllowed
        {
            get
            {
                return Type == ChartType.Line ||
                    Type == ChartType.Scatter;
            }
        }

        private bool ShapeMarkersAllowed
        {
            get
            {
                return Type == ChartType.Line ||
                    Type == ChartType.Scatter;
            }
        }

        private bool HorizontalRangeMarkersAllowed
        {
            get
            {
                return Type == ChartType.Line ||
                    Type == ChartType.Scatter;
            }
        }

        private bool VerticalRangeMarkersAllowed
        {
            get
            {
                return Type == ChartType.Line ||
                    Type == ChartType.Scatter;
            }
        }

        private bool LineStylesAllowed
        {
            get
            {
                return Type == ChartType.Line;
            }
        }

        private bool FillAreaAllowed
        {
            get
            {
                return Type == ChartType.Line;
            }
        }

        private bool PieChartLabelsAllowed
        {
            get
            {
                return Type == ChartType.Pie
                    || Type == ChartType.Pie3D;
            }
        }

        #endregion

        #endregion

        #region Methods

        protected override void PerformDataBinding(IEnumerable data)
        {
            if (data != null)
            {
                DataView dv = data as DataView;
                if (dv != null)
                {
                    ColorChooser colors = new ColorChooser();
                    int colorIndex = 0;
                    foreach (DataRowView row in dv)
                    {
                        DataSet set = new DataSet();
                        set.Color = colors.GetColor(colorIndex++);
                        set.Label = row[0].ToString();
                        for (int i = 1; i < dv.Table.Columns.Count; i++)
                        {
                            float val;
                            if (float.TryParse(row[i].ToString(), out val))
                            {

                            }
                            else
                            {
                                val = 0;
                            }
                            
                            DataPoint point = new DataPoint();
                            point.Value = val;
                            set.DataPoints.Add(point);
                        }
                        DataSets.Add(set);
                    }
                }
            }
        }

        protected override void Render(HtmlTextWriter writer)
        {
            Image img = new Image();
            QueryStringHelper query = new QueryStringHelper();

            query.Add(Keys.Data, GetChartData());
            query.Add(Keys.Type, EnumHelper.GetEnumDescription(Type));
            query.Add(Keys.Size, String.Format("{0}x{1}", Width.IsEmpty ? 200 : Width.Value, Height.IsEmpty ? 200 : Height.Value));
            AddColors(query);
            AddMarkers(query);
            AddAxis(query);
            AddBackgroundFill(query);
            
            if (!String.IsNullOrEmpty(Title))
            {
                query.Add(Keys.Title, Title);
                img.AlternateText = Title;
            }
            AddLabels(query);
            AddLegend(query);

            img.ImageUrl = ServiceUrl + query;
            img.RenderControl(writer);
        }

        private void AddLegend(QueryStringHelper query)
        {
            if (EnableLegend)
                AddLabels(query, Keys.Legend);
            else if (Type != ChartType.Line && Type != ChartType.Pie3D && Type != ChartType.Pie)
            {
                AddLabels(query, Keys.XAxisLabels);
            }
        }

        private void AddLabels(QueryStringHelper query)
        {
            if (PieChartLabelsAllowed)
                AddLabels(query, Keys.Labels);
        }

        private void AddLabels(QueryStringHelper query, string key)
        {
            bool hasLabels = false;
            DelimitedList<string> labels = new DelimitedList<string>("|");
            foreach (DataSet set in DataSets)
            {
                if (!String.IsNullOrEmpty(set.Label))
                {
                    hasLabels = true;
                    labels.Add(set.Label);
                }
            }
            if (hasLabels)
                query.Add(key, labels.ToString());
        }

        private void AddBackgroundFill(QueryStringHelper query)
        {
            DelimitedList<string> fillData = new DelimitedList<string>("|");
            bool hasFill = false;
            foreach (Fill fill in Fills)
            {
                hasFill = true;
                fillData.Add(fill.ToString());
            }
            if (hasFill)
                query.Add(Keys.BackgroundFills, fillData.ToString());
        }

        private void AddAxis(QueryStringHelper query)
        {
            if (Axis != AxisType.None)
            {
                if (Axis == AxisType.All)
                {
                    query.Add(Keys.AxisTypes, EnumHelper.GetEnumDescription(Axis));
                    return;
                }

                DelimitedList<string> axis = new DelimitedList<string>();
                foreach (AxisType axisType in EnumHelper.EnumToList<AxisType>())
                {
                    if (axisType != AxisType.All && axisType != AxisType.XandY && (axisType & Axis) == Axis)
                    {
                        axis.Add(EnumHelper.GetEnumDescription(axisType));
                    }
                }
                query.Add(Keys.AxisTypes, axis.ToString());
            }
        }

        private void AddMarkers(QueryStringHelper query)
        {
            if (!MarkersAllowed)
                return;

            bool hasMarkers = false;
            DelimitedList<string> markers = new DelimitedList<string>("|");
            int dataSetIndex = 0;

            foreach (DataSet dataSet in DataSets)
            {
                int dataPointIndex = 0;
                foreach (DataPoint point in dataSet.DataPoints)
                {
                    if (point.Marker != MarkerType.None)
                    {
                        hasMarkers = true;
                        DelimitedList<string> marker = new DelimitedList<string>();
                        marker.Add(EnumHelper.GetEnumDescription(point.Marker));
                        if(point.MarkerColor == Color.Empty)
                            marker.Add(ColorHelper.ColorToHexString(dataSet.Color));
                        else
                            marker.Add(ColorHelper.ColorToHexString(point.MarkerColor));
                        marker.Add(dataSetIndex.ToString());
                        marker.Add(dataPointIndex.ToString());
                        marker.Add(point.MarkerSize.ToString());
                        markers.Add(marker.ToString());
                    }
                    ++dataPointIndex;
                }
                ++dataSetIndex;
            }
            if(hasMarkers)
                query.Add(Keys.Markers, markers.ToString());
        }

        private void AddColors(QueryStringHelper query)
        {
            bool hasColors = false;
            DelimitedList<string> colors = new DelimitedList<string>();
            foreach (DataSet set in DataSets)
            {
                if (set.Color != Color.Empty)
                {
                    hasColors = true;
                    string color = ColorHelper.ColorToHexString(set.Color);
                    colors.Add(color);
                }
            }
            if(hasColors)
                query.Add(Keys.Colors, colors.ToString());
        }

        private string GetChartData()
        {
            DelimitedList<string> data = new DelimitedList<string>("|");
            foreach (DataSet set in DataSets)
            {
                List<float> values = new List<float>();
                foreach (DataPoint point in set.DataPoints)
                {
                    values.Add(point.Value);
                }
                if(Encoding == ChartDataEncoding.Text || Encoding == ChartDataEncoding.Auto)
                    data.Add(TextEncode(values));
            }
            return "t:" + data.ToString();
        }

        private string TextEncode(IEnumerable<float> values)
        {
            StringBuilder chartData = new StringBuilder();
            foreach (float value in values)
            {
                if (value == float.MinValue || value == float.NaN)
                    chartData.Append("-1");
                else
                    chartData.AppendFormat("{0},", value);
            }
            chartData.Remove(chartData.Length - 1, 1);
            return chartData.ToString();
        }

        private string SimpleEncode(IEnumerable<int> values)
        {
            StringBuilder chartData = new StringBuilder("s:");
            foreach(int value in values)
            {
                if (value == Int32.MinValue)
                    chartData.Append('_');
                else
                {
                    if (value < 0 || value > simpleEncoding.Length - 1)
                        throw new ArgumentOutOfRangeException();
                    chartData.Append(simpleEncoding[value]);
                }
            }
            return chartData.ToString();
        }

        #endregion
    }
}